#!/usr/bin/python
#   Todd File System
#   Copyright (C) <2011>  <Todd longsen.xu@gmail.com>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys, os, time, smtplib, poplib
import email
import mimetypes
import cStringIO
import random
import shutil
from email.mime.text import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.message import Message
from email import Encoders

class Pro:
    head_checkpoint = '[CHECK POINT]'
    head_file = '[FILE]'
    head_operation = '[OPERATION]'
    TFSname = 'tfs'
    def log(self, content):
        print time.strftime("%a, %d %b %Y %H:%M", time.localtime()) + ':' + content

class TFSListNode:
    def __init__(self, l):
        self.no = l[0]
        self.fileno = l[1]
        self.uidl = l[2]
        self.mailno = l[3]
        self.path = l[4]
        self.tag = l[5]
        self.mtime = l[6]
    def tolist(self):
        return [self.no, self.fileno, self.uidl, self.mailno, self.path, self.tag, self.mtime]

class TFSList(list):
    def __init__(self, filename):
        list.__init__(self)
        self.version = '0'
        if os.path.isfile(filename):
            with open(filename, 'rU') as f:
                fl = f.read().split('\n')
                if len(fl) > 0:
                    self.version = fl[0]
                    del(fl[0])
                    for line in fl[:-1]:
                        self.append(TFSListNode(line[:-1].split(':')))
    def append(self, element):
        list.append(self, element)
#       self.sort(key = lambda x:int(x.no))
    def writeListToFile(self, filename):
        with open(filename, 'w') as f:
            f.write(self.version + '\n')
            for i in self:
                f.write(':'.join(i.tolist()) + '\n')

class TFS:
#   def getlmno(self):
#       if not os.path.isfile('lmno'):
#           return 0
#       f = open('lmno', 'r')
#       ans = int(f.read())
#       f.close()
#       return ans
#   def putlmno(self, lmno):
#       f = open('lmno', 'w')
#       f.write(str(lmno))
#       f.close()
    def __init__(self):
#       self.debug = True
        self.debug = False
#       os.chdir('/home/longsen/de/TFS')
        self.popserver = 'pop3.163.com'
        self.smtpserver = 'smtp.163.com'
        self.user = 'toddfilesystem'
        self.pwd = 'whatyoudontknow'
        self.emailadd = self.user + '@163.com'
        if os.path.exists('.TFS') == False:
            os.mkdir('.TFS')
        if os.path.exists('temp') == False:
            os.mkdir('temp')
        if os.path.exists(Pro.TFSname) == False:
            os.mkdir(Pro.TFSname)
        self.clist = TFSList('TFS.list')
#       self.lmno = self.getlmno()
    def retrmail(self, mailno):
        print 'retrmail'
    def sndmail(self, tag, content = ''):
        self.sndmsg = MIMEMultipart('ralated')
        self.sndmsg['From'] = self.emailadd
        self.sndmsg['To'] = self.emailadd
        sub = '[' + tag + ']'
        if tag == 'CREATE' or tag == 'MODIFY':
            sub += ':'.join(content.tolist())
        elif tag == 'DELETE':
            sub += content
        elif tag == 'CHECKPOINT':
            sub += self.clist.version
            part = MIMEBase('application',"octet-stream")
            part.set_payload(open('TFS.list', "rb").read())
            Encoders.encode_base64(part)
            part.add_header('Content-Disposition', 'attachment; filename="%s"' % 'TFS.list')
            self.sndmsg.attach(part)
        elif tag == 'FILE':
            sub += content.fileno + ':' + content.uidl
            part = MIMEBase('application',"octet-stream")
            part.set_payload(open('.TFS/'+content.fileno, "rb").read())
            Encoders.encode_base64(part)
            part.add_header('Content-Disposition', 'attachment; filename="%s"' % content.fileno)
            self.sndmsg.attach(part)
        self.sndmsg['Subject'] = sub
        smtp = smtplib.SMTP()
        smtp.connect(self.smtpserver + ':25')
        smtp.login(self.user, self.pwd)
        smtp.sendmail(self.emailadd, self.emailadd, str(self.sndmsg))
        smtp.quit()
#       self.lmno += 1
    def create(self, ele):
#       self.sndmail('CREATE', ele)
        if self.debug:
            print 'CREATE'+':'.join(ele.tolist())
    def delete(self, no):
#       self.sndmail('DELETE', no)
        if self.debug:
            print 'DELETE' + no
    def modify(self, ele):
#       self.sndmail('MODIFY',ele)
        if self.debug:
            print 'MODIFY' + ':'.join(ele.tolist())
    def upload(self, ele):
        self.sndmail('FILE', ele)
        if self.debug:
            print 'upload ' + ele.fileno
    def checkpoint(self):
        self.sndmail('CHECKPOINT')
        if self.debug:
            print 'CHECKPOINT ' +self.clist.version
#       while True:
#           self.mailbox = poplib.POP3(self.popserver)
#           self.mailbox.user(self.user)
#           self.mailbox.pass_(self.pwd)
#           mailno = self.mailbox.stat()[0]
#           if mailno > 0:
#               fts = '\n'.join(self.mailbox.top(mailno, 0)[1])
#               fsub = email.message_from_string(fts)['Subject']
#               if fsub.find('[FILE]'+fileno) >=0:
#                   break
#           if self.debug:
#               print 'Oops '+str(mailno)
#           self.mailbox.quit()
#           time.sleep(3)
#       uidl = self.mailbox.uidl(mailno).split(' ')[-1]
#       self.mailbox.quit()
#       return str(mailno), uidl
    def locate(self, fileno, uidl):
        li = self.mailbox.stat()[0]
        while li > 0:
            ts = '\n'.join(self.mailbox.top(li, 0)[1])
            sub = email.message_from_string(ts)['Subject']
            if sub.find('[FILE]') >= 0:
                if sub[len('[FILE]'):] == fileno + ':' + uidl:
                    return li
            li -= 1
        return 0
    def download(self, content, tag = 'FILE'):
        if tag == 'FILE':
            mailno = self.locate(content.fileno, content.uidl)
        else:
            mailno = content
        if int(mailno) <= 0:
            if self.debug:
                print 'wrong mailno @ download'
            return
        cm = self.mailbox.retr(mailno)
        buf = '\n'.join(cm[1])
        rmsg = email.message_from_string(buf)
        counter = 0
        for part in rmsg.walk():
            if part.get_content_maintype() == 'multipart':
                continue
            filename = part.get_filename()
            if not filename:
                ext = mimetypes.guess_extension(part.get_content_type())
                if not ext:
                # Use a generic bag-of-bits extension
                    ext = '.bin'
                filename = 'part-%03d%s' % (counter, ext)
            counter += 1
            fp = open('temp/' + filename, 'wb')
            fp.write(part.get_payload(decode=True))
            fp.close()
        if tag == 'FILE':
            fno = rmsg['Subject'][len('[FILE]'):].split(':')[0]
            if os.path.isfile('temp/' + fno):
                if os.path.isfile('.TFS/' + fno):
                    os.remove('.TFS/' + fno)
                shutil.move('temp/' + fno, '.TFS/' + fno)
            else:
                if not os.path.exists(Pro.TFSname + content.path):
                    os.mkdir(Pro.TFSname + content.path)
    def addsymlink(self, ele):
        lk = ''
        for n in range(len(ele.path.split('/')) - 1):
            lk += '../'
        os.system('ln -s ' + lk +'.TFS/' + ele.fileno + ' ' + Pro.TFSname + ele.path)
        f = open('.TFS/'+ele.fileno+'.lns', 'a')
        f.write(ele.path+'\n')
        f.close()
    def delsymlink(self, ele):
        f = open('.TFS/'+ele.fileno+'.lns', 'r')
        tl = f.read().split('\n')
        f.close()
        for c in range(len(tl)):
            if ele.path == tl[c]:
                break
        if c < range(len(tl)):
            del(tl[c])
        f = open('.TFS/'+ele.fileno+'.lns', 'w')
        f.write('\n'.join(tl))
        f.close()
    def synchronize(self):
        if self.debug:
            print '-----------synchronize-------------'
        self.mailbox = poplib.POP3(self.popserver)
        self.mailbox.user(self.user)
        self.mailbox.pass_(self.pwd)
        i = self.mailbox.stat()[0]
        while i > 0:
            ts = '\n'.join(self.mailbox.top(i, 0)[1])
            sub = email.message_from_string(ts)['Subject']
            if sub.find('[CHECKPOINT]') >= 0:
                ver = sub[len('[CHECKPOINT]'):]
                #has newer version(may have bug)
                if ver != self.clist.version:
                    self.download(i, 'CHECKPOINT')
                    tlist = TFSList('temp/TFS.list')
                    for tele in tlist:
                        ise = False
                        for cele in self.clist:
                            if tele.fileno == cele.fileno:
                                ise = True
                                break
                        if ise:
                            if cele.uidl != tele.uidl:
                                self.download(tele)
                                tele.mtime = str(time.time())
                        else:
                            self.download(tele)
                            tele.mtime = str(time.time())
                            if tele.tag == 'FILE':
                                self.addsymlink(tele)
                            else:
                                if not os.path.exists(Pro.TFSname + tele.path):
                                    os.mkdir(Pro.TFSname + tele.path)
                    self.clist = tlist
                    self.clist.writeListToFile('TFS.list')
                break
            i -= 1
        self.mailbox.quit()
#       for i in range(self.lmno + 1, self.mailbox.stat()[0]):
#           ts = '\n'.join(self.mailbox.top(i, 0)[1])
#           sub = email.message_from_string(ts)['Subject']
#           if self.debug:
#               print sub
#           if sub.find('CREATE') >= 0:
#               tele = TFSListNode(sub[len('[CREATE]'):].split(':'))
#               self.download(tele)
#               tele.mtime = time.time()
#               self.clist.append(tele)
#               if os.path.isfile(Pro.TFSname + tele.path):
#                   os.remove(Pro.TFSname + tele.path)
#               elif os.path.isdir(Pro.TFSname + tele.path):
#                   shutil.rmtree(Pro.TFSname + tele.path)
#               if tele.tag == 'FILE':
#               #only works on UNIX
#                   self.addsymlink(tele)
#                   if self.debug:
#                       print 'CREATE FILE '+tele.path
#               elif tele.tag == 'FOLDER':
#                   os.mkdir(Pro.TFSname + tele.path)
#                   if self.debug:
#                       print 'CREATE FOLDER '+tele.path
#           elif sub.find('MODIFY') >= 0:
#               tele = TFSListNode(sub[len('[MODIFY]'):].split(':'))
#               for ii in range(len(self.clist)):
#                   if self.clist[ii].no == tele.no:
#                       break
#               self.clist[ii] = tele
#               self.download(tele)
#               self.clist[ii].mtime = time.time()
#               if self.debug:
#                   print 'MODIFY '+tele.path
#           elif sub.find('DELETE') >= 0:
#               tno = sub[len('[DELETE]'):]
#               for ii in range(len(self.clist)):
#                   if self.clist[ii].no == tno:
#                       break
#               self.delsymlink(self.clist[ii])
#               del(self.clist[ii])
#               if self.debug:
#                   print 'DELETE '+tele.path
#       self.lmno = self.mailbox.stat()[0]
#       self.mailbox.quit()
#       self.clist.writeListToFile('TFS.list')
#       self.putlmno(self.lmno)
#   def garbagecollection(self):
        #TODO
    def readdir(self, fileno):
        f = open('.TFS/' + fileno)
        res = f.read().split('\n')
        del(res[-1])
        return res
    def writedir(self, fileno, content):
        with open('.TFS/' + fileno, 'w') as f:
            for i in content:
                f.write(i + '\n')
    def scanlocalfs(self):
        if self.debug:
            print '--------------------scanlocalfs----------------'
        ism = False
        if len(self.clist) == 0:
        #TFS is empty
            if self.debug:
                print 'TFS is empty'
            tele = TFSListNode(['1', '10001', str(random.randint(10000, 100000)), '0', '/', 'FOLDER', '0'])
            tf = open('.TFS/10001', 'w')
            tf.close()
            self.upload(tele)
            self.clist.append(tele)
            ism = True
            self.create(tele)
        i = 0
        while i < len(self.clist):
            if self.debug:
                print '-----' +str(i) +'----'+self.clist[i].path
            ele = self.clist[i]
            if self.debug:
                print ':'.join(ele.tolist())
            cpath = Pro.TFSname + ele.path
            if ele.tag == 'FILE':
                #may not work in Windows
                #file do not exist
                if not os.path.islink(cpath):
                    if self.debug:
                        print 'do not exist'
                    del(self.clist[i])
                    ism = True
                    i -= 1
                    self.delete(ele.no)
                    self.delsymlink(ele)
                #file has been modified
                elif float(ele.mtime) + 1 < os.stat('.TFS/' + ele.fileno).st_mtime:
                    if self.debug:
                        print 'modified'
                    ele.mailno = '0'
                    ele.uidl = str(random.randint(10000, 100000))
                    ele.mtime = str(os.stat(cpath).st_mtime)
                    self.upload(ele)
                    self.clist[i] = ele
                    ism = True
                    self.modify(ele)
            elif ele.tag == 'FOLDER':
                if not os.path.isdir(cpath):
                    if self.debug:
                        print 'donot exist'
                    del(self.clist[i])
                    ism = True
                    i -= 1
                    self.delete(ele.no)
                    os.remove('.TFS/' + ele.fileno)
                elif float(ele.mtime) + 1 < os.stat(cpath).st_mtime:
                    ft = False
                    ele.mtime = str(os.stat(cpath).st_mtime)
                    cdirlist = os.listdir(cpath)
                    odirlist = self.readdir(ele.fileno)
                    for fname in cdirlist:
                        if self.debug:
                            print fname
                        if not fname in odirlist:
                            ft = True
                            if os.path.isfile(cpath + '/' + fname):
                            #newfile, create
                                nele = TFSListNode([str(random.randint(1, 100000000)), str(random.randint(10000, 100000)),\
                                        str(random.randint(10000, 100000)), '0', ele.path + fname, 'FILE', str(time.time())])
                                shutil.move(cpath + '/' + fname, '.TFS/' + nele.fileno)
                                self.upload(nele)
                                self.addsymlink(nele)
                                self.create(nele)
                                self.clist.append(nele)
                                ism = True
                            elif os.path.isdir(cpath + '/' + fname):
                            #newdir, create
                                nele = TFSListNode([str(random.randint(1, 100000000)), str(random.randint(10000, 100000)),\
                                        str(random.randint(10000, 100000)), '0', ele.path + fname + '/', 'FOLDER', '0'])
                                tf = open('.TFS/' + nele.fileno, 'w')
                                tf.close()
                                self.upload(nele)
                                self.create(nele)
                                self.clist.append(nele)
                                ism = True
                    if ft:
                        if self.debug:
                            print 'modified'
                        self.writedir(ele.fileno, cdirlist)
                        ele.uidl = str(random.randint(10000, 100000))
                        self.upload(ele)
                        self.clist[i] = ele
                        ism = True
                        self.modify(ele)
            i += 1
        if ism:
            self.clist.version = str(random.randint(10000, 100000))
            self.clist.writeListToFile('TFS.list')
            self.checkpoint()
#       self.putlmno(self.lmno)
#       if self.debug:
#           print self.lmno
t = TFS()
#n = 1
while True:
    t.synchronize()
    t.scanlocalfs()
#   n -= 1
    time.sleep(60)

